﻿/*  
 * Copyright 2010 Justin Pealing
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

using System;
using System.Collections.Generic;

namespace Minnow.Web.FastCGI
{
    /// <summary>
    /// Manages the task of reading from a FastCGI connection, splitting recieved messages up by 
    /// connection ID and buffering them.
    /// </summary>
    internal class BufferedConnection
    {
        private List<Record> bufferedRecords = new List<Record>();

        /// <summary>
        /// Initialies a new instance of <see cref="BufferedConnection" /> for an existing connection.
        /// </summary>
        /// <param name="Connection">Connection to buffer.</param>
        public BufferedConnection(Connection Connection)
        {
            this.connection = Connection;
            Connection.BeginReadRecord(new AsyncCallback(readRecordCallback), null);
        }

        /// <summary>
        /// Initialises a new instance of <see cref="BufferedConnection" /> for a new connection.
        /// </summary>
        /// <param name="Port">The port to connect to.</param>
        public BufferedConnection(int Port)
            :this(new Connection(Port))
        {
            
        }

        private void readRecordCallback(IAsyncResult Result)
        {
            Record record = this.connection.EndReadRecord(Result);
            lock (this.bufferedRecords)
            {
                this.bufferedRecords.Add(record);
            }
            // We must ensure that records are read in order, so only initiate the next read once the previous
            // read has been added to the buffer.
            this.connection.BeginReadRecord(new AsyncCallback(readRecordCallback), null);
        }

        /// <summary>
        /// Gets the underlying connection.
        /// </summary>
        public Connection Connection
        {
            get
            {
                return this.connection;
            }
        }
        private Connection connection;

        /// <summary>
        /// Looks for a matching record and removes it from the buffer.
        /// </summary>
        /// <param name="RequestId">Identifies the FastCGI request to which the record belongs.</param>
        /// <param name="RecordType">Type of record to look for.</param>
        /// <returns>First matching record, or null if no matching record is found.</returns>
        /// <remarks>
        /// Guarenteed to return records of a given type for a given request ID in the order that they were recieved.
        /// This method will not block, and will only return records that have already been recieved.
        /// </remarks>
        public Record? GetBufferedRecord(int RequestId, RecordType RecordType)
        {
            // TODO: More efficient buffering
            lock (this.bufferedRecords)
            {
                List<Record> records = this.bufferedRecords.FindAll(record => record.RequestId == RequestId && record.Type == RecordType);
                if (records.Count > 0)
                {
                    bufferedRecords.Remove(records[0]);
                    return records[0];
                }
                return null;
            }
        }

        /// <summary>
        /// Reads a matching record from the buffer, or waits for one to arrive if no record exists in the buffer.
        /// </summary>
        /// <param name="RequestId">Identifies the FastCGI request to which the record belongs.</param>
        /// <param name="RecordType">Type of record to look for.</param>
        /// <returns>First matching record found.</returns>
        /// <remarks>
        /// Guarenteed to return records of a given type for a given request ID in the order that they were recieved.
        /// This method will always return a record, and will block until a matching record has been recieved.
        /// </remarks>
        public Record ReadRecord(int RequestId, RecordType RecordType)
        {
            // TODO: This better!
            while (true)
            {
                Record? returnValue = this.GetBufferedRecord(RequestId, RecordType);
                if (returnValue != null)
                {
                    return returnValue.Value;
                }
            }
        }

        /// <summary>
        /// Clears all buffered records for a given request ID.
        /// </summary>
        /// <param name="RequestId">Identifies the FastCGI request to which the record belongs.</param>
        public void ClearBufferedRecords(int RequestId)
        {
            lock (this.bufferedRecords)
            {
                foreach (Record record in this.bufferedRecords.FindAll(record => record.RequestId == RequestId))
                {
                    this.bufferedRecords.Remove(record);
                }
            }
        }
    }
}
